package csel.model;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;

public class Entity extends GameObject implements Ticked, java.io.Serializable
{
	ArrayList<Observer> observers = new ArrayList<Observer>();
	private static final long serialVersionUID = 1L;
	Tile location;
	Occupation occupation;
	private final Inventory inventory;
	Hashtable<String, EquippableItem> equippedItems;
	String name;
	int lives;
	int strength;
	int agility;
	int intellect;
	int hardiness;
	int experience;
	int speed;
	int lifeLeft;// damage

	private boolean canClimb;
	private boolean canSwim;

	private volatile Direction movingDirection;
	private int ticksSinceLastMove = speed;

	public Entity(Occupation occupation)
	{
		equippedItems = new Hashtable<String, EquippableItem>();
		inventory = new Inventory();
		occupation.setInitialStats(this);
		this.occupation = occupation;
		lifeLeft = 0;
		lives = 5;
		typeId = "player";
		notifyO();
	}

	public void tick()
	{
		ticksSinceLastMove++;
		if (movingDirection != null)
		{
			moveOne(movingDirection);
		}
	}

	public boolean move(Tile nextTile)
	{
		if (nextTile.enter(this))
		{
			location.leave(this);
			location = nextTile;
			notifyO();
			return (true);
		} else
		{
			return (false);
		}
	}

	public boolean move(Direction movementDirection)
	{
		movingDirection = movementDirection;
		direction = movementDirection;
		return (moveOne(movementDirection));
	}

	public boolean moveOne(Direction movementDirection)
	{
		// get correct tile and call move(Tile)
		if (ticksSinceLastMove >= speed)
		{
			Tile nextTile = location.getNeighbor(movementDirection);
			if (nextTile != null)
			{
				ticksSinceLastMove = 0;
				return (move(nextTile));
			} else
			{
				return (false);
			}
		}
		return (false);
	}

	public boolean stop()
	{
		movingDirection = null;
		return (true);
	}

	public boolean canClimb()
	{
		return (canClimb);
	}

	public boolean canSwim()
	{
		return (canSwim);
	}

	public Tile getLocation()
	{
		return location;
	}

	public void setLocation(Tile location)
	{
		this.location = location;
		notifyO();
	}

	public Inventory getInventory()
	{
		return inventory;
	}

	@SuppressWarnings("unchecked")
	public Dictionary<String, EquippableItem> getEquippedItems()
	{
		return (Hashtable<String, EquippableItem>) equippedItems.clone();
	}

	public void setEquippedItem(EquippableItem i)
	{
		EquippableItem oldItem = equippedItems.get(i.getGoesOn());
		if (oldItem != null)
		{
			inventory.addEquippableItem(oldItem);
		}
		equippedItems.put(i.getGoesOn(), i);
		notifyO();
	}

	public void unequipItem(EquippableItem i)
	{
		inventory.addEquippableItem(i);
		inventory.addEquippableItem(equippedItems.remove(i.getGoesOn()));
		notifyO();
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
		notifyO();
	}

	public int getStrength()
	{
		return strength;
	}

	public void setStrength(int strength)
	{
		this.strength = strength;
		notifyO();
	}

	public int getAgility()
	{
		return agility;
	}

	public void setAgility(int agility)
	{
		this.agility = agility;
		notifyO();
	}

	public int getIntellect()
	{
		return intellect;
	}

	public void setIntellect(int intellect)
	{
		this.intellect = intellect;
		notifyO();
	}

	public int getHardiness()
	{
		return hardiness;
	}

	public void setHardiness(int hardiness)
	{
		this.hardiness = hardiness;
		notifyO();
	}

	public int getExperience()
	{
		return experience;
	}

	public void setExperience(int experience)
	{
		this.experience = experience;
		notifyO();
	}

	public int getSpeed()
	{
		return speed;
	}

	public void setSpeed(int speed)
	{
		this.speed = speed;
		notifyO();
	}

	public int getLife()
	{
		/* main structure should be implemented */
		return getLevel() * hardiness;
	}

	public int getLevel()
	{
		/* main structure should be implemented */
		return ((experience / 1000) + 1);
	}

	public int getMana()
	{
		/* main structure should be implemented */
		return (2 * intellect * getLevel()) + getLevel();
	}

	public int getOffense()
	{
		int offense = getLevel() * getStrength();
		Enumeration<String> items = equippedItems.keys();
		String item;
		while (items.hasMoreElements())
		{
			item = items.nextElement();
			offense = equippedItems.get(item).applyOffenseBonus(offense);
		}
		return offense; // + equipped weapon
	}

	public int getDefense()
	{
		/* main structure should be implemented */
		return (agility * getLevel());
	}

	public int getArmor()
	{
		int armor = hardiness;
		Enumeration<String> items = equippedItems.keys();
		String item;
		while (items.hasMoreElements())
		{
			item = items.nextElement();
			armor = equippedItems.get(item).applyArmorBonus(armor);
		}
		return armor; // +get equipped armor
	}

	public void setCanClimb(boolean canClimb)
	{
		this.canClimb = canClimb;
	}

	public void setCanSwim(boolean canSwim)
	{
		this.canSwim = canSwim;
	}

	public int getLives()
	{
		return lives;
	}

	public void setLives(int lives)
	{
		this.lives = lives;
		if (lives <= 0)
		{
			ChronoTrigger.getInstance().stop();
		} 
		notifyO();
	}

	public int getLifeLeft()
	{
		return lifeLeft;
	}

	public void setLifeLeft(int lifeLeft)
	{
		this.lifeLeft = lifeLeft;
		
		if(lifeLeft >= getLife())
		{
			setLives(lives-1);
			this.lifeLeft = 0;
		}
		else if(lifeLeft < 0)
		{
			this.lifeLeft = 0;
		}
		notifyO();
	}
	public Occupation getOccupation()
	{
		return(occupation);
	}
	public void setTicksSinceLastMove(int val)
	{
		this.ticksSinceLastMove = val;
		notifyO();
	}
	public void addObserver(Observer o)
	{
		observers.add(o);
	}
	public boolean removeObserver(Observer o)
	{
		return(observers.remove(o));
	}
	protected void notifyO()
	{
		for(Observer o: observers)
		{
			o.modelUpdate(this);
		}
	}
}
